﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LogX.Logging;
using MMessaging.MessagingEx.Logger;
using TUtils.BasicHelper.Logging;
using TUtils.Threads;

namespace MMessaging.MessagingEx.Common
{
	/// <summary>
	/// Repräsentiert einen Thread, der nach und nach Messages aus einer Messagequeue holt und
	/// sie mit einer initialisierten Messagehandler-Methode verarbeitet.
	/// Innerhalb der Messagehandler-Methode kann man synchron mittels einer WaitOnMessage() Methode 
	/// auf andere Messages warten, so dass der Programmierfluss nicht durch 
	/// asynchronen Nachrichtenaustausch unterbrochen wird. 
	/// Besonders ist hierbei, dass man auf diese Weise nach außen hin mit anderen Services asynchron 
	/// kommunizieren kann, aber intern synchron und singlethreaded programmiert.
	/// Und obwohl man singlethreaded programmiert, können lang andauernde parallele Prozesse simuliert werden.
	/// </summary>
	/// <remarks>
	/// Man kann hierbei tatsächlich in bestimmten Grenzen so tun, als würde dabei jede Message durch denselben Thread
	/// verarbeitet werden, so dass man auf Multithread-Synchronisierungen verzichten kann.
	/// Tatsächlich wird aber intern jede Message durch einen anderen Thread verarbeitet, wobei sich
	/// jedoch die Threads untereinander immer abwechseln und niemals gleichzeitig Messages verarbeiten.
	/// Jeder Aufruf der Methode WaitOnMessage() führt dazu, dass ein anderer interner Thread drankommt.
	/// Hierdurch werden die Messages pseudo-parallel verarbeitet.
	/// 
	/// 
	/// Nebenwirkungen:
	/// Es ist darauf zu achten, dass die zu initialisierende Messagehandler-Methode nie lange Zeit 
	/// läuft ohne WaitOnMessage(..) aufzurufen, da das sonst die 
	/// Pseudoparallelität der anderen Message-Verarbeitungsprozesse behindern würde.
	/// Für lange Hintergrundprozesse sollte ein Hintergrundthread erzeugt werden oder (besser)
	/// eine neue Instanz der Klasse "MMessageHandlerThread".
	/// 
	/// 
	/// WaitOnMessage(..) darf nicht aufgerufen werden, wenn ein  Synchronisationobjekt vom aufrufenden 
	/// Thread noch gesperrt ist. Folgendes ist verboten:
	/// <code>
	/// lock(syncObj)
	/// {
	///		...
	///		WaitOnMessage(msg=>msg is MyMessage);
	///		...
	/// }
	/// </code>
	/// Das könnte zu einem Deadlock führen. Synchronisation-Konstrukte sollten eher die Ausnahme bleiben,
	/// da ein maßgebliches Ziel des angewendeten Musters ja gerade der Verzicht auf Synchronisierungen ist.
	/// In Ausnahmefällen ist folgendes erlaubt:
	/// <code>
	/// lock(syncObj)
	/// {
	///		...
	/// }
	///	...
	///	WaitOnMessage(msg=>msg is MyMessage);
	/// ...
	/// lock(syncObj)
	/// {
	///		...
	/// }
	/// </code>
	/// 
	/// Zu berücksichtigen ist auch, dass es andere Sperrmechanismenarten gibt als 
	/// "lock": Z.B. das exklusive Öffnen einer Datei. Auch diese können grundsätzlich
	/// in Programmen Deadlocks verursachen. Das ist kein Phänomen dieser Library.
	/// Man sollte möglichst nicht Objekte sperren und dann im gesperrten Zustand auf etwas warten,
	/// dass seinerseits die Objekte benötigt.
	/// </remarks>
	/// <typeparam name="MESSAGETYPE"></typeparam>
	internal class MMessageHandlerThread<MESSAGETYPE> : MThread<MMessageHandlerThread<MESSAGETYPE>>
		where MESSAGETYPE : class
	{
		#region private fields

		private object _syncObj = new object();

		/// <summary>
		/// Handler Methode
		/// </summary>
		private List<Action<IMTask<MESSAGETYPE>>> _messageHandlers = new List<Action<IMTask<MESSAGETYPE>>>();
		/// <summary>
		/// Messagequeue, von der die zu verarbeitenden Messages stammen
		/// </summary>
		private IMChannelEndOut<MESSAGETYPE> _messageQueue;
		/// <summary>
		/// alle Threads dieses MessageHandlerThreads als auch der mit ihm 
		/// verbundenen MessageHandlerThreads.
		/// </summary>
		private List<MTaskThread<MESSAGETYPE>> _allThreads = new List<MTaskThread<MESSAGETYPE>>();
		private const string LOGXID = "348238759672549";

		private int _maxCountOfThreads;

		#endregion

		#region constructor

		/// <summary>
		/// HIDDEN
		/// </summary>
		/// <param name="signalHandler"></param>
		/// <param name="threadMethod"></param>
		/// <param name="obj"></param>
		/// <param name="threadName"></param>
		/// <returns></returns>
		public new TThreadX<MMessageHandlerThread<MESSAGETYPE>> Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			Action<IThreadContext> threadMethod,
			MMessageHandlerThread<MESSAGETYPE> obj,
			string threadName
			)
		{
			throw new ApplicationException("zskdjlksklfuweifkgjk");
		}

		/// <summary>
		/// Initialisiert den HandlerThread vollständig.
		/// Bitte nicht vergessen, anschließend StartAsync() aufzurufen.
		/// </summary>
		/// <param name="messageHandlerName">
		/// Name des HandlerThreads. Dient in Logging - Ausgaben zur späteren Wiedererkennung
		/// </param>
		/// <param name="messageQueue">
		/// Der Ausgang des Message-Kanals, von dem dieser MessageHandlerThread seine
		/// zu verarbeitenden Messages bezieht.
		/// </param>
		/// <param name="messageHandler">
		/// Die Methodensignatur ist 
		/// <code><![CDATA[
		/// void Handler(IMTask<MESSAGETYPE> task)
		/// ]]></code>
		/// Die übergebene Methode darf nicht lange Zeit laufen ohne task.WaitOnMessage(..)
		/// aufzurufen, da das sonst die 
		/// Pseudoparallelität der anderen Message-Verarbeitungsprozesse behindern würde.
		/// Für lange Hintergrundprozesse sollte ein Hintergrundthread erzeugt werden oder (besser)
		/// eine neue Instanz der Klasse "MMessageHandlerThread", mit der dann mittels Messages 
		/// kommuniziert wird.
		/// </param>
		/// <param name="maxCountOfThreads">
		/// -1, if no limit
		/// </param>
		/// <returns>
		/// this
		/// </returns>
		public MMessageHandlerThread<MESSAGETYPE> Init(
			string messageHandlerName,
			IMChannelEndOut<MESSAGETYPE> messageQueue,
			int maxCountOfThreads,
			Action<IMTask<MESSAGETYPE>> messageHandler)
		{
			AddHandler(messageHandler);
			_messageQueue = messageQueue;
			_maxCountOfThreads = maxCountOfThreads;

			base.Init(
				OnMessageHandlerThreadSignal,
				MessageHandlerThreadMethod,
				this,
				messageHandlerName);

			return this;
		}

		#endregion

		#region private methods
		
		/// <summary>
		/// wird aufgerufen, wen sich der Zustand des internen Controllerthreads ändert.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="signal"></param>
		/// <param name="e"></param>
		private void OnMessageHandlerThreadSignal(IThreadContext context, SignalEnum signal, Exception e)
		{
			ControllerThreadSignalChanged(context, signal, e);
		}

		/// <summary>
		/// die interne ThreadMethode.
		/// Sie holt endlos (bis zum Aufruf der Methode CancelStart()) Messages aus der
		/// gegebenen Messagequeue und lässt sie jeweils in einem separaten Thread verarbeiten.
		/// Außerdem stößt sie alle früheren Messageverarbeitungsthreads an, die noch nicht 
		/// mit ihrer Verarbeitung fertig sind, weil sie noch auf etwas warten.
		/// </summary>
		/// <param name="context"></param>
		private void MessageHandlerThreadMethod(
			IThreadContext context)
		{
			while (!IsCanceling)
			{
				// fetch next message from queue
				MESSAGETYPE message = _messageQueue.WaitForMessage(MMessagingHelper.CANCEL_WAIT_INTERVALL);

				if (message != null )
				{
					TLog.Instance.SetThreadAttachedLogValue(
						new LogValue(
							MPredefinedLoggingValueIDs.CurrentMessageID,
							() => message.GetHashCode().ToString(CultureInfo.InvariantCulture)));
					TLog.Instance.SetThreadAttachedLogValue(
						new LogValue(
							MPredefinedLoggingValueIDs.CurrentMessageContent,
							() => message.ToString()));
					TLog.Instance.LogInfo(
						loggingInstance: this,
						formattedText: "message received");
				}

#				if DEBUG
				var debuggableMsg = message as IMDebugableMessage;
				if (debuggableMsg != null && debuggableMsg.Break)
				{
					// set a break point here ! ZVDZJHFESJ
					int a = 0;
				}
#				endif

				// tick all waiting tasks
				foreach (var waitingTask in _allThreads.Where(task=>task.TaskState == MTaskThread<MESSAGETYPE>.StateEnum.WAITING))
				{
					bool taskIsWaitingForThisMessage = false;

					try
					{
						// check, if waiting task has to reactivate
						taskIsWaitingForThisMessage = waitingTask.IsMessageWaitingFor(message);
					}
#					if !THROWEXCEPTION
					catch (Exception e)
					{
						// condition check causes exception
						TLog.Instance.LogException(e);
						// kill task
						waitingTask.CancelAsync();
						_allThreads.Remove(waitingTask);
					}
#					else
					finally
					{
					}
#					endif

					// if task is waiting for this message
					if (taskIsWaitingForThisMessage)
					{
						// give control to that task and wait
						waitingTask.CurrentMessage = message;
						GiveControlAwayAndWait(waitingTask);

						if (IsCanceling)
							return;
					}
				}

				if (message != null)
				{

					foreach (var messageHandler in MessageHandlers)
					{
						// process message ....

						// get a free task from task pool
						var freeTask = _allThreads.FirstOrDefault(task => task.TaskState == MTaskThread<MESSAGETYPE>.StateEnum.FREE);

						// if there could be a free task
						if ((freeTask != null) ||
						    (_allThreads.Count < _maxCountOfThreads))
						{
							// if we must create a new task
							if (freeTask == null)
							{
								freeTask = new MTaskThread<MESSAGETYPE>().Init(
									signalHandler: OnTaskThreadSignal,
									threadName: ThreadName + " Task " + _allThreads.Count,
									messageHandlerThread: this,
									messageHandler: messageHandler);
								_allThreads.Add(freeTask);
								freeTask.StartAsync();
							}

							// give control to that new task and wait
							freeTask.CurrentMessage = message;
							GiveControlAwayAndWait(freeTask);
						}
						else // if no free thread could be taken or created
						{
							OnMaxCountOfThreadsReached();

							// message will be ignored due to overload ...
						}
					}
				}
			}
		}

		/// <summary>
		/// wird aufgerufen, wenn sich der Threadzustand eines Subthreads verändert.
		/// Läuft im Threadcontext des internen Tasks.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="signal"></param>
		/// <param name="e"></param>
		private void OnTaskThreadSignal(
			TThreadX<MTaskThread<MESSAGETYPE>>.IThreadContext context,
			TThreadX<MTaskThread<MESSAGETYPE>>.SignalEnum signal,
			Exception e)
		{
			SubTaskThreadSignalChanged(context, signal, e);
		}

		#endregion

		#region public / protected / overrides

		public void AddHandler(Action<IMTask<MESSAGETYPE>> messageHandler)
		{
			lock (_syncObj)
			{
				_messageHandlers.Add(messageHandler);
			}
		}

		public void RemoveHandler(Action<IMTask<MESSAGETYPE>> messageHandler)
		{
			lock (_syncObj)
			{
				_messageHandlers.Remove(messageHandler);
			}
		}

		private IEnumerable<Action<IMTask<MESSAGETYPE>>> MessageHandlers
		{
			get
			{
				lock (_syncObj)
				{
					return _messageHandlers.ToList();
				}
			}
		}

		public delegate void ControllerThreadSignalChangedFunc(IThreadContext context, SignalEnum signal, Exception e);
		public delegate void SubTaskThreadSignalChangedFunc(
			TThreadX<MTaskThread<MESSAGETYPE>>.IThreadContext context,
			TThreadX<MTaskThread<MESSAGETYPE>>.SignalEnum signal,
			Exception e);

		public event ControllerThreadSignalChangedFunc ControllerThreadSignalChanged = (context, signal, e) =>{};
		public event SubTaskThreadSignalChangedFunc SubTaskThreadSignalChanged = (context, signal, e) =>{};

		/// <summary>
		/// wird aufgerufen, wenn der Thread beendet worden ist.
		/// </summary>
		protected override void OnFinallyLeaving()
		{
			foreach (var task in _allThreads)
			{
				task.CancelAsync();
			}

			base.OnFinallyLeaving();
		}

		/// <summary>
		/// called, when trying to create more threads than allowed
		/// </summary>
		protected virtual void OnMaxCountOfThreadsReached()
		{
			string msgTxt = string.Format(
				"MMessageHandlerThread {0}: max count of threads reached _maxCountOfThreads={1}",
				this.ThreadName,
				this._maxCountOfThreads);
#			if DEBUG
			throw new ApplicationException("has87d2j3db " + msgTxt);
#			else
			LoggX.Log(LogTypeEnum.ERROR, "MMessaging",msgTxt);
#			endif
		}

		#endregion
	}
}
